//////////////////////////////////////////////////////////////////////////
//Author	:	Ross Conroy ross.conroy@tees.ac.uk
//Date		:	15/05/2014
//
//This class extracts data from the currently running game/replay
//Each time the tick method is called a new line is added to the data file
//All data is stored in the following CSV Format
//GameTime,PlayerName,PlayerRace,Force,UnitID,UnitType,IsUnderAttack,
//IsAttacking,UnitHealth,PosX,PosY,ClosestEnemy,PosX,PosY,Distance,EnemyVisible,
//////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "ExtractData.h"
#include <sstream>
#include <cmath> 
#include <math.h>

ExtractData::ExtractData(void)
{
	_csvRows.push_back("GameTime,PlayerName,PlayerRace,Force,UnitID,UnitType,IsUnderAttack,IsAttacking,UnitHealth,PosX,PosY,ClosestEnemyID,PosX,PosY,Distance,EnemyVisible,TimeSinceUnitLastLogged,VisibilityRadius");
	_startClock = clock();
	_enemyBattleDistanceMultiplyer = 2;
	//_nearestEnemySearchDistance = 500;
}

//determines if information about the unit should be included in the datafile based on if
//a nearby enemy was found, the type of unit (ignore buildings) and if the unit is
//gathering resources (only interested in battles, ignore workers)
bool ExtractData::IncludeInData(Unit * unit, Unit * closestEnemy)
{
	bool extract = true;	

	UnitType uType = unit->getType();

	//float enemyBattleDistance = uType.sightRange() * _enemyBattleDistanceMultiplyer;

	//buildings cannot fight so ignore them
	if(uType.isBuilding())
	{
		extract = false;
	}
	//Remove any units that are currently gathering resources
	if (unit->isCarryingMinerals() || unit->isCarryingGas() || unit->isGatheringGas() || unit->isGatheringMinerals())
	{
		extract = false;
	}

	/*if(unit->isIdle())
	{
		extract = false;
	}*/

	if(closestEnemy != NULL)
	{
		//if(GetDistanceBetweenPoisitions(unit->getPosition(), closestEnemy->getPosition()) > enemyBattleDistance)
		//{
		//	extract = false;
		//}		
	}
	else
	{
		extract  = false;
	}

	return extract;
}

//This method gets data about current replay/game and turns it into CSV rows
void ExtractData::Tick()
{
	stringstream gameTime;
	clock_t gameTimeClock = clock() - _startClock;
	gameTime << gameTimeClock << ",";

	for(set<Player*>::iterator p=Broodwar->getPlayers().begin();p!=Broodwar->getPlayers().end();p++)
	{
		Player * player = *p;

		if (!(*p)->getUnits().empty() && !(*p)->isNeutral())
		{	
			stringstream playerInfo;
			playerInfo << gameTime.str();
			playerInfo << player->getName() << ",";
			playerInfo<< player->getRace().getName() << ","; 
			playerInfo<< player->getForce() << ",";

			for(set<Unit*>::const_iterator u = player->getUnits().begin(); u != player->getUnits().end(); u++)
			{
				Unit * unit = *u;
				UnitType uType = unit->getType();
				float enemyBattleDistance = uType.sightRange() * _enemyBattleDistanceMultiplyer;

				Unit* closestEnemy = FindNearestEnemy(unit, enemyBattleDistance);

				if(IncludeInData(unit, closestEnemy))
				{					
					clock_t lastLogged = 0;
					if(_unitLastlogged.find(unit->getID()) != _unitLastlogged.end())
					{
						lastLogged = _unitLastlogged[unit->getID()];
					}
					stringstream unitInfo;
					unitInfo << playerInfo.str();
					unitInfo << unit->getID() << ",";
					unitInfo << unit->getType().getName() << ",";	
					unitInfo << HasUnitBeenAttacked(unit) << ","; // Change this

					bool attacking = unit->isAttacking() || unit->isStartingAttack() || unit->isAttackFrame();
					unitInfo << attacking << ",";

					unitInfo << unit->getHitPoints() << ",";
					unitInfo << unit->getPosition().x() << ",";
					unitInfo << unit->getPosition().y() << ",";					

					if(closestEnemy != NULL)
					{				
						unitInfo << closestEnemy->getID() << ",";
						unitInfo << closestEnemy->getPosition().x() << ",";
						unitInfo << closestEnemy->getPosition().y() << ",";
						unitInfo << GetDistanceBetweenPoisitions(unit->getPosition(), closestEnemy->getPosition()) << ",";

						bool enemyVisible = closestEnemy->isVisible(player);
						unitInfo << enemyVisible << ",";
					}
					else
					{
						unitInfo << ",,,,,";
					}

					_unitLastlogged[unit->getID()] = gameTimeClock;
					unitInfo << (gameTimeClock - lastLogged) << ",";	
					unitInfo << uType.sightRange() << ",";

					_csvRows.push_back(unitInfo.str());
				}
			}
		}
	}
}

//Compares current health with the previous health for the unit to determine if it has been attacked
bool ExtractData::HasUnitBeenAttacked(Unit * unit)
{
	bool hasBeenAttacked = false;

	if(_previousHealth[unit->getID()] > unit->getHitPoints())
	{		
		hasBeenAttacked = true;
	}

	_previousHealth[unit->getID()] = unit->getHitPoints();

	if(unit->isUnderAttack())
	{
		hasBeenAttacked = true;
	}	

	return hasBeenAttacked;
}

//Finds the nearest enemy to the unit
Unit * ExtractData::FindNearestEnemy(Unit * unit, float searchDistance)
{
	Player * player = unit->getPlayer();

	Unit* closestEnemy=NULL;

	set<Unit*> enemyUnits = unit->getUnitsInRadius(searchDistance);
	//set<Unit*> enemyUnits = Broodwar->getUnitsInRadius(unit->getPosition(), _nearestEnemySearchDistance);

	for (set<Unit*>::const_iterator uit = enemyUnits.begin(); uit != enemyUnits.end(); uit++)
	{
		Unit * nearUnit = *uit;
		Player * nearUnitPlayer = nearUnit->getPlayer();

		if(player->getName() != nearUnitPlayer->getName() && !nearUnitPlayer->isNeutral())
		{
			if (closestEnemy==NULL || GetDistanceBetweenPoisitions(unit->getPosition(), nearUnit->getPosition())
				< GetDistanceBetweenPoisitions(unit->getPosition(), closestEnemy->getPosition()))
			{
				UnitType uType = nearUnit->getType();
				if (!uType.isBuilding())
				{
					closestEnemy = *uit;
				}				
			}
		}
	}


	/*for(set<Player *>::const_iterator pit = Broodwar->getPlayers().begin(); pit != Broodwar->getPlayers().end(); pit++)
	{
		Player * play = *pit;

		if(play->getName() != player->getName() && !play->isNeutral())
		{
			set<Unit *> enemyUnits = play->getUnits();
			
			for(set<Unit*>::const_iterator uit = enemyUnits.begin(); uit!= enemyUnits.end(); uit++)
			{
				if (closestEnemy==NULL || GetDistanceBetweenPoisitions(unit->getPosition(), (*uit)->getPosition())
					< GetDistanceBetweenPoisitions(unit->getPosition(), closestEnemy->getPosition()))
				{
					closestEnemy = *uit;
				}
			}
		}
	}*/

	return closestEnemy;
}

//Uses Pythagoras to find distance between 2 points
float ExtractData::GetDistanceBetweenPoisitions(Position pos1, Position pos2)
{
	int diffx = pos1.x() - pos2.x();
	int diffy = pos1.y() - pos2.y();
	float tmp = (diffx*diffx) + (diffy*diffy);

	float distance = sqrt(tmp);

	return distance;
}

bool ExtractData::SaveData(string fileName)
{
	ofstream file;
	file.open(fileName.c_str());

	for(list<string>::iterator it = _csvRows.begin(); it != _csvRows.end(); it++)
	{
		file << *it << endl;
	}

	file.close();

	return true;
}
